/****************************************************************************
 Copyright (c) 2012-2012 lijiejun
 Copyright (c) 2010-2012 cocos2d-x.org
 Copyright (c) 2008-2010 Ricardo Quesada
 Copyright (c) 2011      Zynga Inc.
 
 http://www.cocos2d-x.org
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
 
 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 ****************************************************************************/

package org.cocos2d.sprite_nodes
{
	import flash.display3D.Context3DBlendFactor;
	
	import org.cocos2d.basenodes.CCNode;
	import org.cocos2d.cocoa.CCObject;
	import org.cocos2d.cocoa.CCPoint;
	import org.cocos2d.cocoa.CCRect;
	import org.cocos2d.cocoa.CCSize;
	import org.cocos2d.support.CCPointExtension;
	import org.cocos2d.support.ccBlendFunc;
	import org.cocos2d.support.ccColor3B;
	import org.cocos2d.support.ccColor4B;
	import org.cocos2d.support.ccMacros;
	import org.cocos2d.support.ccTypes;
	import org.cocos2d.support.ccV3F_C4B_T2F_Quad;
	import org.cocos2d.textures.CCTexture2D;
	import org.cocos2d.textures.CCTextureAtlas;
	import org.cocos2d.textures.CCTextureCache;

	public class CCSprite extends CCNode
	{
		protected var m_nOpacity : int;
		
		public function get Opacity() : int
		{
			return m_nOpacity;
		}
		
		public function set Opacity(value : int) : void
		{
			m_nOpacity = value;
		}
		
		protected var m_sColor : ccColor3B;
		
		public function get Color() : ccColor3B
		{
			return m_sColor;
		}
		
		public function set Color(value : ccColor3B) : void
		{
			m_sColor = value;
		}
		
		public static function create(pszFileName : String) : CCSprite
		{
			var pobSprite : CCSprite = new CCSprite();
			if (pobSprite && pobSprite.initWithFile(pszFileName))
			{
				return pobSprite;
			}
			
			return null;
		}
		
		public static function create(pSpriteFrame : CCSpriteFrame) : CCSprite
		{
			var pobSprite : CCSprite = new CCSprite();
			if (pobSprite != null && pobSprite.initWithSpriteFrame(pSpriteFrame))
			{
				return pobSprite;
			}
			
			return null;
		}
		
		public function CCSprite()
		{
		}
		
		public function SET_DIRTY_RECURSIVELY() : void
		{
			if (m_pobBatchNode && !m_bRecursiveDirty)
			{
				m_bRecursiveDirty = true;
				Dirty = true;
				if (m_bHasChildren)
				{
					setDirtyRecursively(true);
				}
			}
		}
		
		public function setDirtyRecursively(bValue : Boolean) : void
		{
			m_bRecursiveDirty = bValue;
			Dirty = bValue;
			
			if (m_bHasChildren)
			{
				for (var i : uint = 0; i < m_pChildren.length; i++)
				{
					var pChild : CCSprite = m_pChildren[i]  as CCSprite();
					if (pChild)
					{
						pChild.setDirtyRecursively(true);
					}
				}
			}
		}
		
		public function initWithTexture(pTexture : CCTexture2D, rect : CCRect) : Boolean
		{
			return initWithTexture(pTexture, rect, false);
		}
		
		public function initWithTexture(pTexture : CCTexture2D, rect : CCRect, rotated : Boolean) : Boolean
		{
			m_bRecursiveDirty = false;
			Dirty = false;
			
			m_bOpacityModifyRGB = true;
			m_nOpacity = 255;
			m_sColor = ccColor3B.ccWHITE;
			m_sColorUnmodified = ccColor3B.ccWHITE;
			
			m_sBlendFunc.src = ccMacros.CC_BLEND_SRC;
			m_sBlendFunc.dst = ccMacros.CC_BLEND_DST;
			
			m_bFlipX = m_bFlipY = false;
			
			AnchorPoint = CCPointExtension.ccp(0.5, 0.5);
			
			m_obOffsetPosition = CCPoint.Zero;
			
			m_bHasChildren = false;
			
			m_sQuad = new ccV3F_C4B_T2F_Quad();
			
			var tmpColor : ccColor4B = new ccColor4B(255, 255, 255, 255);
			m_sQuad.bl.colors = tmpColor;
			m_sQuad.br.colors = tmpColor;
			m_sQuad.tl.colors = tmpColor;
			m_sQuad.tr.colors = tmpColor;
			
			Texture = pTexture;
			setTextureRect(rect, rotated, rect.size);
			
			BatchNode = null;
			
			return true;
		}
		
		public function initWithSpriteFrame(pSpriteFrame : CCSpriteFrame) : Boolean
		{
			if (pSpriteFrame == null)
				throw new Error("");
			
			var bRet : Boolean = initWithTexture(pSpriteFrame.Texture, pSpriteFrame.Rect);
			DisplayFrame = pSpriteFrame;
			
			return bRet;
		}
		
		public function initWithFile(pszFilename : String) : Boolean
		{
			if (pszFilename == null) throw new Error("Invalid filename for sprite");
			
			var pTexture : CCTexture2D = CCTextureCache.sharedTextureCache().addImage((pszFilename);
			if (pTexture)
			{
				var rect : CCRect = CCRect.Zero;
				rect.size = pTexture.ContentSize;
				return initWithTexture(pTexture, rect);
			}
			
			return false;
		}
		
		public function setTextureRect(rect : CCRect) : void
		{
			setTextureRect(rect, false, rect.size);
		}
		
		public function setTextureRect(rect : CCRect, rotated : Boolean, untrimmedSize : CCSize)
		{
			m_bRectRotated = rotated;
			
			ContentSize = untrimmedSize;
			VertexRect = rect;
			TextureCoords = rect;
			
			var relativeOffset : CCPoint = m_obUnflippedOffsetPositionFromCenter;
			
			if (m_bFlipX)
			{
				relativeOffset.x = -relativeOffset.x;
			}
			if (m_bFlipY)
			{
				relativeOffset.y = -relativeOffset.y;
			}
			
			m_obOffsetPosition.x = relativeOffset.x + (m_tContentSize.width - m_obRect.size.width) / 2;
			m_obOffsetPosition.y = relativeOffset.y + (m_tContentSize.height - m_obRect.size.height) / 2;
			
			if (m_pobBatchNode)
			{
				Dirty = true;
			}
			else
			{
				var x1 : Number = 0 + m_obOffsetPosition.x;
				var y1 : Number = 0 + m_obOffsetPosition.y;
				var x2 : Number = x1 + m_obRect.size.width;
				var y2 : Number = y1 + m_obRect.size.height;
				
				m_sQuad.bl.vertices = ccTypes.vertex3(x1, y1, 0);
				m_sQuad.br.vertices = ccTypes.vertex3(x2, y1, 0);
				m_sQuad.tl.vertices = ccTypes.vertex3(x1, y2, 0);
				m_sQuad.tr.vertices = ccTypes.vertex3(x2, y2, 0);
			}
		}
		
		public function set VertexRect(rect : CCRect) : void
		{
			m_obRect = rect;
		}
		
		public function set DisplayFrame(pNewFrame : CCSpriteFrame) : void
		{
			m_obUnflippedOffsetPositionFromCenter = pNewFrame.Offset;
			
			var pNewTexture : CCTexture2D = pNewFrame.Texture;
			
			if (pNewTexture != m_pobTexture)
			{
				Texture = pNewTexture;
			}
			
			m_bRectRotated = pNewFrame.Rotated();
			setTextureRect(pNewFrame.Rect, m_bRectRotated, pNewFrame.OriginalSize());
		}
		
		protected function set TextureCoords(rect : CCRect) : void
		{
			var rectInPixels = ccMacros.CC_RECT_POINTS_TO_PIXELS(rect);
			
			var tex : CCTexture2D = m_pobBatchNode != null ? m_pobTextureAtlas.Texture : m_pobTexture;
			if (tex != null)
			{
				return;
			}
			
			var atlasWidth : Number = tex.PixelsWide;
			var atlasHeight : Number = tex.PixelsHigh;
			
			var left : Number,  right : Number, top : Number, bottom : Number;
			
			if (m_bRectRotated)
			{
				if (ccMacros.CC_FIX_ARTIFACTS_BY_STRECHING_TEXEL)
				{
					left = (2 * rect.origin.x + 1) / (2 * atlasWidth);
					right = left + (rect.size.height * 2 - 2) / (2 * atlasWidth);
					top = (2 * rect.origin.y + 1) / (2 * atlasHeight);
					bottom = top + (rect.size.width * 2 - 2) / (2 * atlasHeight);
				}
				else
				{
					left = rect.origin.x / atlasWidth;
					right = (rect.origin.x + rect.size.height) / atlasWidth;
					top = rect.origin.y / atlasHeight;
					bottom = (rect.origin.y + rect.size.width) / atlasHeight;
				}
				
				if (m_bFlipX)
				{
					// for actionscript, no ref, no generic, how to implement CC_SWAP?
					var temp : Number = top;
					top = bottom;
					bottom = temp;
				}
				
				if (m_bFlipY)
				{
					var temp : Number = left;
					left = right;
					right = temp;
				}
				
				m_sQuad.bl.texCoords.u = left;
				m_sQuad.bl.texCoords.v = top;
				m_sQuad.br.texCoords.u = left;
				m_sQuad.br.texCoords.v = bottom;
				m_sQuad.tl.texCoords.u = right;
				m_sQuad.tl.texCoords.v = top;
				m_sQuad.tr.texCoords.u = right;
				m_sQuad.tr.texCoords.v = bottom;
			}
			else
			{
				if (ccMacros.CC_FIX_ARTIFACTS_BY_STRECHING_TEXEL)
				{
					left = (2 * rect.origin.x + 1) / (2 * atlasWidth);
					right = left + (rect.size.width * 2 - 2) / (2 * atlasWidth);
					top = (2 * rect.origin.y + 1) / (2 * atlasHeight);
					bottom = top + (rect.size.height * 2 - 2) / (2 * atlasHeight);
				}
				else
				{
					left = rect.origin.x / atlasWidth;
					right = (rect.origin.x + rect.size.width) / atlasWidth;
					top = rect.origin.y / atlasHeight;
					bottom = (rect.origin.y + rect.size.height) / atlasHeight;
				}
				
				if (m_bFlipX)
				{
					var temp : Number = left;
					left = right;
					right = temp;
				}
				
				if (m_bFlipY)
				{
					var temp : Number = top;
					top = bottom;
					bottom = temp;
				}
				
				m_sQuad.bl.texCoords.u = left;
				m_sQuad.bl.texCoords.v = bottom;
				m_sQuad.br.texCoords.u = right;
				m_sQuad.br.texCoords.v = bottom;
				m_sQuad.tl.texCoords.u = left;
				m_sQuad.tl.texCoords.v = top;
				m_sQuad.tr.texCoords.u = right;
				m_sQuad.tr.texCoords.v = top;
			}
		}
		
		protected function updateBlendFunc() : void
		{
			if (m_pobBatchNode)
				throw new Error("CCSprite: updateBlendFunc doesn't work when the sprite is rendered using a CCSpriteSheet");
			
			if (!m_pobTexture || !m_pobTexture.hasPremultipliedAlpha())
			{
				m_sBlendFunc.src = Context3DBlendFactor.SOURCE_ALPHA;
				m_sBlendFunc.dst = Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA;
				OpacityModifyRGB = false;
			}
			else
			{
				m_sBlendFunc.src = ccMacros.CC_BLEND_SRC;
				m_sBlendFunc.dst = ccMacros.CC_BLEND_DST;
				OpacityModifyRGB = true;
			}
		}
		
		protected var m_pobTextureAtlas : CCTextureAtlas;
		protected var m_pobBatchNode : CCSpriteBatchNode;
		
		protected var m_bDirty : Boolean;
		
		public function get Dirty() : Boolean
		{
			return m_bDirty;
		}
		
		public function set Dirty(value : Boolean) : void
		{
			m_bDirty = value;
		}
		
		protected var m_sBlendFunc : ccBlendFunc = new ccBlendFunc();
		
		protected var m_pobTexture : CCTexture2D;
		
		public function set Texture(texture : CCTexture2D) : void
		{
			if (m_pobBatchNode != null && texture.Name != m_pobBatchNode)
				throw new Error("CCSprite: Batched sprites should use the same texture as the batchnode");
			
			if (texture != null && texture is CCTexture2D)
				throw new Error("setTexture expects a CCTexture2D. Invalid argument");
			
			if (!m_pobBatchNode && m_pobTexture != texture)
			{
				m_pobTexture = texture;
				updateBlendFunc();
			}
		}
		
		public function get Texture() : CCTexture2D
		{
			return m_pobTexture;
		}
		
		protected var m_obRect : CCRect;
		protected var m_bRectRotated : Boolean;
		
		protected var m_obOffsetPosition : CCPoint;
		protected var m_obUnflippedOffsetPositionFromCenter : CCPoint;
		
		protected var m_sQuad : ccV3F_C4B_T2F_Quad;
		
		protected var m_bRecursiveDirty : Boolean;
		protected var m_bHasChildren : Boolean;
		
		protected var m_sColorUnmodified : ccColor3B;
		protected var m_bOpacityModifyRGB : Boolean;
		
		public function set OpacityModifyRGB(bValue : Boolean) : void
		{
			var oldColor : ccColor3B = m_sColor;
			m_bOpacityModifyRGB = bValue;
			m_sColor = oldColor;
		}
		
		public function get OpacityModifyRGB() : Boolean
		{
			return m_bOpacityModifyRGB;
		}
		
		protected var m_bFlipX : Boolean;
		protected var m_bFlipY : Boolean;
	}
}